/**
 * 设计一个函数，可以限制请求的并发，同时请求结束之后，调用callback函数
 * sendRequest(requestList:,limits,callback):void
 * sendRequest(
 * 	[
 * 		() => request('1'),
 * 		() => request('2'),
 * 		() => request('3'),
 * 		() => request('4'),
 * 	],
 *	3, //并发数
		(res) => {
			console.log(res)
		}
 * )
 *
 * 其中request 可以是：
 * function request(url, time = 1){
 * 	return new Promise((resolve, reject) => {
 * 		setTimeout(() => {
 * 			console.log('请求结束：' + url);
 * 			if(Math.random() > 0.5){
 * 				resolve('成功')
 * 			}else{
 * 				reject('错误;')
 * 			}
 * 		}, time * 1e3)
 * 	})
 * }

 * 
 */

/**
 * 明确概念
 *
 * ⚠️ 这里有几个概念需要明确一下
 *
 * 并发：并发是多个任务同时交替的执行（因为cpu执行指令的速度非常之快，它可以不必按顺序一段代码一段代码的执行，这样效率反而更加低下），这样看起来就是一起执行的，所以叫并发。
 *
 * 并行：可以理解为多个物理cpu或者有分布式系统，是真正的'同时'执行
 *
 * 并发控制：意思是多个并发的任务，一旦有任务完成，就立刻开启下一个任务
 *
 * 切片控制：将并发任务切片的分配出来，比如10个任务，切成2个片，每片有5个任务，当前一片的任务执行完毕，再开始下一个片的任务，这样明显效率没并发控制那么高了
 */

/**
 * 思路
 *
 * 首先执行能执行的并发任务，根据并发的概念，每个任务执行完毕后，捞起下一个要执行的任务。
 *
 * 将关键步骤拆分出合适的函数来组织代码
 *
 * - 循环去启动能执行的任务
 * - 取出任务并且推到执行器执行
 * - 执行器内更新当前的并发数，并且触发捞起任务
 * - 捞起任务里面可以触发最终的回调函数和调起执行器继续执行任务
 */

/**
 * 核心代码是判断是当你【有任务执行完成】，再去判断是否有剩余还有任务可执行。
 * 可以先维护一个pool（代表当前执行的任务），利用await Promise.race这个 pool，不就知道是否有任务执行完毕了吗？
 */

const sendRequest = async (requestList, limits, callback) => {
	const promises = [] // 维护一个 promise 队列
	const pool = new Set() // 当前的并发池, Set 结构方便删除

	// 没事并发执行所有任务
	for (let request of requestList) {
		// 开始执行前, 先 await 判断当前的并发任务是否超过限制
		if (pool.size >= limits) {
			// 这里因为没有try catch ，所以要捕获一下错误，不然影响下面微任务的执行
			await Promise.race(pool).catch((err) => err)
		}

		const p = request() // 拿到promise

		// 删除请求结束后，从pool里面移除
		const cb = () => {
			pool.delete(p)
		}
		// 注册下then的任务
		promise.then(cb)
		pool.add(p)
		promises.push(p)
	}

	// 等最后一个for await 结束，这里是属于最后一个 await 后面的 微任务

	// 注意这里其实是在微任务当中了，当前的promises里面是能确保所有的promise都在其中(前提是await那里命中了if)

	Promise.allSettled(promises).then(callback, callback)
}
